package orbito.entity;

import java.awt.Color;
import java.util.Random;

import orbito.util.DoubleRange;
import orbito.util.IntRange;
import orbito.util.Vec2;

public class PlanetoidSystemFactory {
	
	// Generated system names.
	static String[] names = new String[] { 
			"Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta", "Theta", "Iota", 
			"Kappa", "Lambda", "Mu", "Nu", "Xi", "Omicron", "Pi", "Rho", "Sigma", "Tau", 
			"Upsilon", "Phi", "Chi", "Psi", "Omega" };
	
	/**
	 * This will generate a simple representation of our solar system, sol.
	 * 
	 * @param rand The randomizer used for generating the system.
	 * @return     The solar system, sol.
	 */
	public static PlanetoidSystem generateSol(Random rand) {
		// Settings.
		int earthSatelliteCount = 0;
		
		Planetoid planet, satellite;
		PlanetoidSystem planetSys, satSys;
		
		// Create the sun and all the planets.
		Planetoid sun = new Planetoid("The Sun", new Vec2(), new Vec2(), 1.9891e30, 6.955e8, new Color(0xF36B07), null);
		PlanetoidSystem sol = new PlanetoidSystem(sun);
		
		// mercury
		planet = PlanetoidFactory.generateOrbitingPlanetoid(
				"Mercury", 3.3022e23, 2.439e6, new Color(0x8C5C1C), sun, 5.79091e10, false, rand);
		planetSys = new PlanetoidSystem(planet);
		sol.addChildSystem(planetSys);
		
		// venus
		planet = PlanetoidFactory.generateOrbitingPlanetoid(
				"Venus", 1.0820893e11, 6.0518e6, new Color(0xCB3100), sun, 1.0820893e11, false, rand);
		planetSys = new PlanetoidSystem(planet);
		sol.addChildSystem(planetSys);
		
		// earth
		planet = PlanetoidFactory.generateOrbitingPlanetoid(
				"Earth", 5.9736e24, 6.375e6, new Color(0x656E99), sun, 1.49598261e11, false, rand);
		planetSys = new PlanetoidSystem(planet);
		sol.addChildSystem(planetSys);
		
		// earth, the moon
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"The Moon", 7.3477e22, 1.7371e6, new Color(0x5D5A55), planet, 3.84399e8, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// earth, satellites (realistic: 8500)
		for (int i = 0; i < earthSatelliteCount; i++) {
			double mass = new DoubleRange(500, 2000).getRandomInRange(rand);
			double radius = 1;
			double dist = new DoubleRange(planet.getRadius() + 200000, planet.getRadius() + 42000000).getRandomInRange(rand);
			
			satellite = PlanetoidFactory.generateOrbitingPlanetoid(
					"Satellite #" + i, mass, radius, new Color(0xFFFFFF), planet, dist, false, rand);
			satSys = new PlanetoidSystem(satellite);
			planetSys.addChildSystem(satSys);
		}
		
		// mars
		planet = PlanetoidFactory.generateOrbitingPlanetoid(
				"Mars", 3.3022e23, 2.439e6, new Color(0x876F4D), sun, 2.279391e11, false, rand);
		planetSys = new PlanetoidSystem(planet);
		sol.addChildSystem(planetSys);
		
		// mars, phobos
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Phobos", 1.072e16, 11100, new Color(0x808080), planet, 9.3772e6, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// mars, deimos
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Deimos", 1.48e15, 6200, new Color(0xA78F6D), planet, 2.346e7, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// jupiter
		planet = PlanetoidFactory.generateOrbitingPlanetoid(
				"Jupiter", 1.8986e27, 6.9911e7, new Color(0xAC998B), sun, 7.78547200e11, false, rand);
		planetSys = new PlanetoidSystem(planet);
		sol.addChildSystem(planetSys);
		
		// jupiter, io
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Io", 8.9319e22, 1.8213e6, new Color(0xB29E45), planet, 4.217e8, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// jupiter, europa
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Europa", 4.8e22, 1.569e6, new Color(0xB5B2C3), planet, 6.709e8, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// jupiter, ganymede
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Ganymede", 1.4819e23, 2.6341e6, new Color(0x7F7762), planet, 1.0704e9, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// jupiter, callisto
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Callisto", 1.075938e23, 2.4103e6, new Color(0x735B5B), planet, 1.8827e9, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// saturn
		planet = PlanetoidFactory.generateOrbitingPlanetoid(
				"Saturn", 5.6846e26, 6.0268000e7, Color.GRAY, sun, 1.433449370e12, false, rand);
		planetSys = new PlanetoidSystem(planet);
		sol.addChildSystem(planetSys);
		
		// saturn, mimas
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Mimas", 3.7493e19, 1.982e5, new Color(0x808080), planet, 1.85539e8, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// saturn, enceladus
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Enceladus", 1.08022e20, 2.521e5, new Color(0x808080), planet, 2.37948e8, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// saturn, tethys
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Tethys", 6.17449e20, 5.311e5, new Color(0xAF957E), planet, 2.94619e8, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// saturn, dione
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Dione", 1.095452e21, 5.61400e5, new Color(0xA0A0A0), planet, 3.77396e8, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// saturn, rhea
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Rhea", 2.306518e21, 7.638e5, new Color(0xB2B2B2), planet, 5.27108e8, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// saturn, titan
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Titan", 1.3452e23, 2.576e6, new Color(0xFCD552), planet, 1.22187e9, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// saturn, hyperion
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Hyperion", 0.56199e19, 1.35e5, new Color(0xA09484), planet, 1.481009e9, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// saturn, iapetus
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Iapetus", 1.805635e21, 7.345e5, new Color(0x413C38), planet, 3.56082e9, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// saturn, phoebe
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Phoebe", 0.8292e19, 1.065e5, new Color(0xACACAC), planet, 1.2955759e10, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// uranus
		planet = PlanetoidFactory.generateOrbitingPlanetoid(
				"Uranus", 8.6810e25, 2.5559000e7, new Color(0x92B7BD), sun, 2.876679082e12, false, rand);
		planetSys = new PlanetoidSystem(planet);
		sol.addChildSystem(planetSys);
		
		// uranus, puck
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Puck", 2.9e18, 81000, new Color(0xACACAC), planet, 8.6004444e7, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// uranus, miranda
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Miranda", 6.59e19, 2.358e5, new Color(0xB1B1B1), planet, 1.29390e8, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// uranus, ariel
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Ariel", 1.353e21, 5.789e5, new Color(0x6D5B59), planet, 1.91020e8, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// uranus, umbriel
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Umbriel", 1.172e21, 5.847e5, new Color(0xEBB76B), planet, 2.66000e8, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// uranus, titania
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Titania", 3.527e21, 7.884e5, new Color(0xC9B9A9), planet, 4.35910e8, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// uranus, oberon
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Oberon", 3.014e21, 7.614e5, new Color(0xB99B99), planet, 5.83520e8, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// neptune
		planet = PlanetoidFactory.generateOrbitingPlanetoid(
				"Neptune", 1.0243e26, 2.4764e7, new Color(0x5881DD), sun, 4.503443661e12, false, rand);
		planetSys = new PlanetoidSystem(planet);
		sol.addChildSystem(planetSys);
		
		// neptune, proteus
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Proteus", 4.4e19, 2.10e5, new Color(0x808080), planet, 1.17647e8, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// neptune, triton
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Triton", 2.14e22, 1.3534e6, new Color(0x7A867C), planet, 3.54759e8, true, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// neptune, nereid
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Nereid", 3.1e19, 170000, new Color(0x808080), planet, 5.513787e9, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		// pluto
		planet = PlanetoidFactory.generateOrbitingPlanetoid(
				"Pluto", 1.305e22, 1.153e6, new Color(0x808080), sun, 5.874000000e12, false, rand);
		planetSys = new PlanetoidSystem(planet);
		sol.addChildSystem(planetSys);
		
		// pluto, charon
		satellite = PlanetoidFactory.generateOrbitingPlanetoid(
				"Charon", 1.52e21, 6.035e5, new Color(0xB8A29A), planet, 1.9571e7, false, rand);
		satSys = new PlanetoidSystem(satellite);
		planetSys.addChildSystem(satSys);
		
		return sol;
	}
	
	/**
	 * Generates a random system of orbiting planetoids.
	 * 
	 * @param centroidMass   The mass of the initial centroid. E.g., in our solar system, the centroid is the sun.
	 * @param centroidRadius The radius of the initial centroid.
	 * @param distRange      The range of distances immediate children may have from the starting centroid.
	 * @param depthRange     The depth range of the system. E.g., our solar system's depth ranges from 1 to 2, because the 
	 *                       sun has planets, and some planets have moons. If those moons again would have satellites orbiting them, the depth range 
	 *                       would be 1 to 3. If all planets had moons, the depth range would be 2 to 3, and if all moons had satellites, the 
	 *                       depth range would be 3 to 3.
	 * @param satCountRange  The range of number of potential satellites a planetoid may have. E.g., in our solar 
	 *                       system, Venus has no satellites, so the lower range is 0, but Saturn has 62 moons, making the range 0 to 62.
	 * @param rand           The randomizer used for generating the system.
	 * @return               The randomly generated planetoid system.
	 */
	public static PlanetoidSystem generateRandomSystem(double centroidMass, double centroidRadius,
			DoubleRange distRange, IntRange depthRange, IntRange satCountRange, Random rand) {
		
		// Create the initial centroid.
		Planetoid centroid = new Planetoid(createName("", 0), new Vec2(), new Vec2(), centroidMass, centroidRadius, Color.YELLOW, null);
		PlanetoidSystem root = new PlanetoidSystem(centroid);
		
		// Randomly select number of children.
		int childrenCount = satCountRange.getRandomInRange(rand);
		
		// Create child systems.
		for (int i = 0; i < childrenCount; i++) {
			// Create a name.
			String childName = createName(centroid.getName(), i);
			
			// Randomly select distance from parent.
			double childDist = distRange.getRandomInRange(rand);
			
			// Generate and add child system.
			PlanetoidSystem childSystem = generateChildSystem(centroid, childName, childDist, depthRange, 1, satCountRange, rand);
			root.addChildSystem(childSystem);
		}
		
		// Return root.
		return root;
	}
	
	private static PlanetoidSystem generateChildSystem(Planetoid parent, String name, double parentDist,
			IntRange depth, int currentDepth, IntRange satelliteCount, Random rand) {
		
		// Randomly select the mass.
		DoubleRange massRange = new DoubleRange(parent.getMass() * 1e-7, parent.getMass() * 1e-3);
		double mass = massRange.getRandomInRange(rand);
		
		// Randomly select the radius.
		DoubleRange radiusRange = new DoubleRange(parent.getRadius() * 1e-3, parent.getRadius() * 1e-1);
		double radius = radiusRange.getRandomInRange(rand);
		
		// Randomly select the fill color.
		Color fillColor = new Color(rand.nextInt(0x1000000));
		
		// Create the child and its system.
		Planetoid node = PlanetoidFactory.generateOrbitingPlanetoid(name, mass, radius, fillColor, parent, parentDist, false, rand);
		PlanetoidSystem system = new PlanetoidSystem(node);
		
		// Randomly select target depth.
		int targetDepth = depth.getRandomInRange(rand);
		
		// If target depth is not reached, make children for this system.
		if (currentDepth < targetDepth) {
			// Calculate range of distances from this to its parents.
			DoubleRange distRange = new DoubleRange(radius + parentDist * 1e-3, radius + parentDist * 1e-2);
			
			// Randomly select number of children.
			int childrenCount = satelliteCount.getRandomInRange(rand);
			
			// Create child systems.
			for (int i = 0; i < childrenCount; i++) {
				// Create a name.
				String childName = createName(node.getName(), i);
				
				// Randomly select distance from parent.
				double childDist = distRange.getRandomInRange(rand);
				
				// Generate and add child system.
				PlanetoidSystem childSystem = generateChildSystem(node, childName, childDist, depth, currentDepth + 1, satelliteCount, rand);
				system.addChildSystem(childSystem);
			}
		}
			
		// Return system.
		return system;
	}
	
	private static String createName(String baseName, int i) {
		String name = names[i % names.length];
		int number = 1 + i / names.length;
		name += number > 1 ? "" + number : "";
		return baseName + " " + name;
	}
}
